package qq2564874169.tuaotuao.fx;


import qq2564874169.tuaotuao.fx.utils.ClassUtil;
import qq2564874169.tuaotuao.fx.utils.ConvertUtil;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

public class Dynamic {
    private StringKeyMap<Object> map = new StringKeyMap<>();
    private boolean loadAll;
    private boolean loaded;

    public static List<Dynamic> asList(List items) {
        List<Dynamic> list = new ArrayList<>();
        for (Object item : items) {
            list.add(new Dynamic(item));
        }
        return list;
    }

    public static Dynamic setNew(Action1T<Map<String, Object>> action) {
        Map<String, Object> map = new HashMap<>(4);
        action.execute(map);
        return new Dynamic(map);
    }

    public Dynamic(Map<String, Object> values) {
        map.putAll(values);
    }

    public Dynamic(Object obj) {
        load(obj);
    }

    public Dynamic() {

    }

    public Dynamic(boolean loadAll) {
        this.loadAll = loadAll;
    }

    public Dynamic(boolean loadAll, Object obj) {
        this.loadAll = loadAll;
        load(obj);
    }

    private void load(Object obj) {
        if (loaded || obj == null) {
            return;
        }
        if (obj instanceof Dynamic src) {
            for (String name : src.map.keySet()) {
                map.put(name, src.map.get(name));
            }
            return;
        }
        if (obj instanceof Map objMap) {
            for (Object k : objMap.keySet()) {
                map.put(k.toString(), objMap.get(k));
            }
            return;
        }
        if (obj instanceof Collection items) {
            int index = 0;
            for (Object item : items) {
                map.put(String.valueOf(index++), item);
            }
            return;
        }
        if (obj instanceof Enumeration en) {
            int index = 0;
            while (en.hasMoreElements()) {
                map.put(String.valueOf(index++), en.nextElement());
            }
            return;
        }
        if (obj.getClass().isArray()) {
            Object[] items = (Object[]) obj;
            int index = 0;
            for (Object item : items) {
                map.put(String.valueOf(index++), item);
            }
            return;
        }
        List<Field> fields = ClassUtil.fields(obj.getClass());
        for (Field item : fields) {
            if (item.getName().contains("$")) {
                continue;
            }
            if (Modifier.isStatic(item.getModifiers())) {
                continue;
            }
            if (item.getDeclaringClass() == Dynamic.class) {
                continue;
            }
            if (loadAll == false && item.getModifiers() != Modifier.PUBLIC) {
                continue;
            }
            if (map.containsKey(item.getName())) {
                continue;
            }
            item.trySetAccessible();
            Object value = ClassUtil.getValue(item, obj);
            map.put(item.getName(), value);
        }
        FxStream<Method> methods = FxStream.create(ClassUtil.getMethods(obj.getClass()));
        for (Method item : methods.toList()) {
            if (Modifier.isStatic(item.getModifiers())) {
                continue;
            }
            if (item.getDeclaringClass() == Dynamic.class) {
                continue;
            }
            if (loadAll == false && item.getModifiers() != Modifier.PUBLIC) {
                continue;
            }
            if (map.containsKey(item.getName())) {
                continue;
            }
            if (item.getParameterCount() != 0) {
                continue;
            }
            if (item.getReturnType() == void.class || item.getReturnType() == Void.class) {
                continue;
            }
            Method set = methods.first(i -> i.getName().equals(item.getName()) && i != item).orElse(null);
            if (set == null) {
                continue;
            }
            if (set.getParameterCount() != 1) {
                continue;
            }
            if (set.getParameters()[0].getType() != item.getReturnType()) {
                continue;
            }
            if (set.getReturnType() != set.getDeclaringClass() && set.getReturnType() != void.class) {
                continue;
            }
            try {
                map.put(item.getName(), item.invoke(obj));
            } catch (IllegalAccessException | InvocationTargetException e) {
                //...
            }
        }
        loaded = true;
    }

    public List<String> getFields() {
        load(this);
        return FxStream.create(map.keySet()).toList();
    }

    public Object get(String field) {
        load(this);
        return map.get(field);
    }

    public <T> T get(String field, Class<T> clazz) {
        return get(field, clazz, null);
    }

    public <T> T get(String field, Class<T> clazz, T defaultValue) {
        load(this);
        T rs = ConvertUtil.change(get(field), clazz);
        return rs == null ? defaultValue : rs;
    }

    public String getString(String field) {
        load(this);
        Object value = get(field);
        return value == null ? null : value.toString();
    }

    public Dynamic remove(String field) {
        load(this);
        map.remove(field);
        return this;
    }

    public Object removeAndGet(String field) {
        load(this);
        return map.remove(field);
    }

    public Dynamic set(String field, Object value) {
        load(this);
        map.put(field, value);
        return this;
    }

    public Dynamic rename(String oldName, String newName) {
        load(this);
        if (map.containsKey(oldName) && oldName.equalsIgnoreCase(newName) == false) {
            map.put(newName, map.remove(oldName));
        }
        return this;
    }

    public Map<String, Object> toMap() {
        load(this);
        return map;
    }

    public Integer getInt(String field) {
        return get(field, Integer.class);
    }

    public Date getDate(String field) {
        return get(field, Date.class);
    }

    public Boolean getBool(String field) {
        return get(field, Boolean.class);
    }

    public Long getLong(String field) {
        return get(field, Long.class);
    }

    public Dynamic getDynamic(String field) {
        load(this);
        Object value = map.getOrDefault(field, null);
        return value == null ? null : new Dynamic(value);
    }

    public int size() {
        return map.size();
    }

    @Override
    public String toString() {
        return map.toString();
    }

    @Override
    public int hashCode() {
        return map.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Dynamic dynamic = (Dynamic) o;

        return map.equals(dynamic.map);
    }
}
